Multi-channel devices: pick which channels TeamTalk uses per device - #39
Multi-channel devices: pick which channels TeamTalk uses per device#39rfiorentino1 wants to merge 1 commit into
Conversation
On an interface with more than a single stereo pair, TeamTalk no longer has to sit on channels 1/2. Each picker sits next to the device popup it belongs to, and every choice is remembered per CoreAudio device UID. - Output (new): OutputChannelSelection (.auto / mono channel N / odd-even stereo pair) in Preferences > Audio, shown for devices with >2 outputs. ttac_render_planes hardcoded planes 0/1 and zeroed the rest; it now takes left/right plane indices (right < 0 = sum to mono) and validates them against devCh in C. The engine publishes the pair packed into ONE word (planeMapCell) so the render thread reads a coherent pair lock-free, the same benign single-word pattern as the gain/mute cells. No AudioUnit rebind: a routing change is heard on the next buffer, so it applies unconditionally rather than through a device reinit. startImpl re-resolves against the new device's channel count, so swapping a 32-out for stereo falls back to 1/2 instead of going silent. - Input (moved): the channel preset picker was buried in the Microphone settings block; it now sits under the Input Device popup. Gated at >= 2 channels, not > 2, because a plain stereo device already had real options there (Input 1 mono, mono mix) and moving the control must not delete them. - Stream a Device or Application (new): the capture backend kept "the first two channels"; it now resolves an InputChannelPreset into clamped source indices at start (mono feeds both sides so it stays centered, mono mix averages). Threaded as a channelPreset: parameter rather than baked into DeviceStreamCaptureSpec, whose Equatable and persistence token would have churned. The picker lives in MediaStreamSourceViewController next to the source button, hidden rather than dimmed when it does not apply - a disabled control is still announced by VoiceOver - and it announces the new routing on selection, since an NSButton's VALUE cannot be overridden. Note for future work on this store: rootStore.$preferences fires on willSet, so rootStore.preferences still holds the OLD value inside that sink. Reading it there resolved the device the user had just switched away from and kept the output picker hidden; the sink now uses the value it is handed, with a direct refresh from updateSelectedDevices as well (which is why the input picker never had the bug). OutputChannelPickerVisibilityTests covers it and was verified to fail without the fix.
|
Reviewed against current The design holds up well, and a few things are worth saying out loud because they were done right:
Below is what I'd like changed before this goes in. Push back on any of it if you disagree — several are judgement calls and you know this hardware better than I do. Tests1. The regression test only fires on the right hardware. 2. Same test breaks the suite's rule. 3. The UserDefaults cleanup isn't guaranteed. Code4. "Glitch-free" is a little optimistic ( 5. The two input pickers disagree on the threshold. 6. 7. Two sources of truth in one function. Docs8. The user guide landed on Wording (VoiceOver)9. "Auto" on its own doesn't say it means outputs 1/2 — something like "Auto (outputs 1/2)" would. And on a 24-out interface the menu is 49 items with all 24 monos ahead of the 12 pairs, when the pair is the common case; putting pairs first would cut the keyboard trip. Minor: "Outputs 5/6 stereo" reads as "5 slash 6" in speech — "Outputs 5 and 6" may land better. You're the better judge of all three. Happy to merge once (1)–(3) and (8) are handled — the rest is polish that can follow. Two things I couldn't verify and that want your ears: the possible click in (4), and the stream-sheet picker, since you noted your iD44 pass predates the port onto the new sheet. |
On an interface with more than a single stereo pair, TeamTalk no longer has to sit on channels 1/2. Each picker sits next to the device popup it belongs to, and every choice is remembered per CoreAudio device UID.
Output (new).
OutputChannelSelection(auto / mono channel N / odd-even stereo pair) in Preferences ▸ Audio, shown for devices with more than two outputs. The RT path was hardcoded:ttac_render_planeswrote planes 0/1 and zeroed the rest. It now takes left/right plane indices — right < 0 sums to mono — and validates them againstdevChin C. The engine publishes the pair packed into ONE word (planeMapCell) so the render thread reads a coherent pair lock-free, the same benign single-word pattern as the existing gain and mute cells. No AudioUnit rebind: a routing change is heard on the next buffer, so it applies unconditionally rather than through a device reinit.startImplre-resolves against the new device's channel count, so swapping a 32-out for a stereo device falls back to 1/2 instead of going silent.Input (moved, not new). The channel-preset picker already existed, buried in the Microphone settings block; it now sits under the Input Device popup. Gated at >= 2 channels rather than > 2 on purpose — a plain stereo device already had real options there (Input 1 mono, mono mix), and moving the control must not delete them.
Stream a Device or Application (new). The capture backend kept "the first two channels"; it now resolves an
InputChannelPresetinto clamped source indices at start (mono feeds both sides so it stays centred, mono mix averages). Threaded as achannelPreset:parameter rather than baked intoDeviceStreamCaptureSpec, whoseEquatableand persistence token would have churned.The picker for that last one lives in
MediaStreamSourceViewControllernext to the source button: a plainNSButtonwith the role overridden to.popUpButtonand a menu popped on press, matching what you built there. It's in a stack with the source button so hiding it closes the gap, and it is hidden rather than dimmed when it doesn't apply — a disabled control is still announced by VoiceOver, and there's nothing to say about this one. Selecting a routing posts an announcement, for the same reason your source button does: anNSButton's VALUE can't be overridden.One implementation note worth recording, since it cost real debugging time:
rootStore.$preferencesfires on willSet, sorootStore.preferencesstill holds the OLD value inside that sink. Reading it there resolved the device the user had just switched away from and kept the output picker hidden entirely. The sink now uses the value it is handed, with a direct refresh fromupdateSelectedDevicesas well — which is why the input picker never had the bug.OutputChannelPickerVisibilityTestscovers it and was verified to fail without the fix.Cut from current
main(1.10.0); builds clean and the suite passes. Verified on real hardware (Audient iD44, 24 out / 22 in) — the mix lands on the chosen output channels — though that pass was on an earlier base, before the source picker was ported onto the new sheet, so the stream-side picker deserves your own listen.